package main

import (
	"encoding/binary"
	"errors"
	"fmt"
	netpack "im/im_client/proto"
	"io"
	"log"
	"net"
	"runtime"
	"time"

	"google.golang.org/protobuf/proto"
)

type Handler func(proto.Message, *Connection) error

type Connection struct {
	conn net.Conn
	send chan []byte

	handlerCallback map[netpack.MessageID]Handler
	player          *netpack.DBPlayer
}

type ChatClient struct {
	conns      map[*Connection]bool
	register   chan *Connection
	unregister chan *Connection

	registerPlayer chan *Connection
	playerConns    map[int64]*Connection
}

func PanicOnError(err error) {
	if err != nil {
		panic(err)
	}
}

func NewChatClient() *ChatClient {
	return &ChatClient{
		conns:          make(map[*Connection]bool),
		register:       make(chan *Connection),
		unregister:     make(chan *Connection),
		registerPlayer: make(chan *Connection),
		playerConns:    make(map[int64]*Connection),
	}
}

type TcpClient interface {
	ClientConn(net.Conn)
}

func ClientTcp(client TcpClient, server_addr string) {
	conn, err := net.Dial("tcp", server_addr)
	PanicOnError(err)
	client.ClientConn(conn)
}

func (c *Connection) register(cl *ChatClient) {
	chatMessage := &ChatMessage{}
	chatMessage.chatClient = cl
	loginMessage := &LoginMessage{}
	loginMessage.chatClient = cl
	errorMessage := &ErrorMessage{}
	errorMessage.chatClient = cl

	c.handlerCallback[netpack.MessageID_SC_LOGIN] = loginMessage.Login
	c.handlerCallback[netpack.MessageID_SC_CHAT] = chatMessage.Chat
	c.handlerCallback[netpack.MessageID_SC_ERROR] = errorMessage.Error
	c.handlerCallback[netpack.MessageID_SC_CREATE_CHATROOM] = chatMessage.CreateRoom
	c.handlerCallback[netpack.MessageID_SC_JOIN_CHATROOM] = chatMessage.JoinRoom
}

func (c *Connection) output() {
	defer c.close()
	for m := range c.send {
		err := binary.Write(c.conn, binary.BigEndian, int32(len(m)))
		if err != nil {
			log.Println(err)
			break
		}

		var n int
		n, err = c.conn.Write(m)
		if err != nil {
			log.Println(err)
			break
		}

		if n != len(m) {
			log.Println("short wirte")
			break
		}
	}
}

func (c *Connection) close() {
	log.Println("close connection")
	c.conn.Close()
}

func (c *Connection) newMessage(id netpack.MessageID) (proto.Message, error) {
	if id == netpack.MessageID_SC_LOGIN {
		return &netpack.SCLogin{}, nil
	} else if id == netpack.MessageID_SC_CHAT {
		return &netpack.SCChat{}, nil
	} else if id == netpack.MessageID_SC_ERROR {
		return &netpack.ErrorMessage{}, nil
	} else if id == netpack.MessageID_SC_CREATE_CHATROOM {
		return &netpack.SCCreateChatRoom{}, nil
	} else if id == netpack.MessageID_SC_JOIN_CHATROOM {
		return &netpack.SCJoinChatRoom{}, nil
	} else {
		return nil, fmt.Errorf("new message not found %v id", id)
	}
}

func (c *Connection) input() {
	for {
		message, err := c.readMessage()
		if err != nil {
			log.Println(err)
			break
		}

		pro := &netpack.Protocol{}
		err = proto.Unmarshal(message, pro)
		if err != nil {
			log.Println(err)
			break
		}

		callback, ok := c.handlerCallback[pro.GetId()]
		if !ok {
			log.Printf("SC protocol id not found %v", pro.GetId())
			break
		}

		msg, err := c.newMessage(pro.Id)
		if err != nil {
			log.Println(err)
			break
		}

		err = proto.Unmarshal(pro.Msg, msg)
		if err != nil {
			log.Println(err)
			break
		}

		err = callback(msg, c)
		if err != nil {
			log.Println(err)
			break
		}
	}
}

func (c *Connection) readMessage() (message []byte, err error) {
	var length int32
	err = binary.Read(c.conn, binary.BigEndian, &length)
	if err != nil {
		return nil, err
	}

	if length > 65536 || length < 0 {
		return nil, errors.New("invalid length")
	}

	message = make([]byte, int(length))
	if length > 0 {
		var n int
		n, err = io.ReadFull(c.conn, message)
		if err != nil {
			return nil, err
		}

		if n != len(message) {
			return message, errors.New("short read")
		}
	}
	return message, nil
}

func (c *Connection) Send(id netpack.MessageID, m proto.Message) error {
	msg, err := proto.Marshal(m)
	if err != nil {
		return err
	}

	sc_pro := &netpack.Protocol{}
	sc_pro.Id = id
	sc_pro.Msg = msg

	sendb, err := proto.Marshal(sc_pro)
	if err != nil {
		return err
	}

	c.send <- sendb
	return nil
}

func (c *Connection) login() {
	pro := &netpack.Protocol{}
	pro.Id = netpack.MessageID_CS_LOGIN

	csLogin := &netpack.CSLogin{
		Account:    "account",
		PlayerName: "player_name",
		Email:      "diaodhjiahd@163.com",
		Age:        20,
	}

	c.Send(netpack.MessageID_CS_LOGIN, csLogin)
}

func (cl *ChatClient) ClientConn(conn net.Conn) {
	c := &Connection{
		conn:            conn,
		send:            make(chan []byte, 1024),
		handlerCallback: make(map[netpack.MessageID]Handler),
		player:          &netpack.DBPlayer{},
	}

	cl.register <- c
	defer func() { cl.unregister <- c }()

	c.register(cl)
	c.login()

	go c.output()
	c.input()
}

func (s *ChatClient) Dial(num int, server_addr string) {
	for i := 0; i < num; i++ {
		go ClientTcp(s, server_addr)
	}
}

func (s *ChatClient) Run() {
	ticks := time.Tick(time.Second * 10)
	for {
		select {
		case c := <-s.register:
			s.conns[c] = true
		case c := <-s.registerPlayer:
			s.playerConns[c.player.PlayerId] = c
		case c := <-s.unregister:
			delete(s.conns, c)
			delete(s.playerConns, c.player.PlayerId)
			close(c.send)
		case _ = <-ticks:
			log.Println(len(s.conns), runtime.NumGoroutine())
		}
	}
}

type LoginMessage struct {
	chatClient *ChatClient
}

func (l *LoginMessage) Login(msg proto.Message, c *Connection) error {
	scLogin, ok := msg.(*netpack.SCLogin)
	if !ok {
		return errors.New("parse login message error")
	}

	c.player = scLogin.DbPlayerData
	{
		csChat := &netpack.CSChat{
			Channel: netpack.RoomChannel_HALL_ROOM,
			Msg:     "hall golang, go go go",
			RoomId:  -1,
		}
		c.Send(netpack.MessageID_CS_CHAT, csChat)
	}

	{
		csCreateRoom := &netpack.CSCreateChatRoom{
			RoomInfo: "",
			RoomName: "room_name",
		}
		c.Send(netpack.MessageID_CS_CREATE_CHATROOM, csCreateRoom)
	}

	log.Println(scLogin)
	l.chatClient.registerPlayer <- c
	return nil
}

type ChatMessage struct {
	chatClient *ChatClient
}

func (ch *ChatMessage) Chat(msg proto.Message, c *Connection) error {
	scChat, ok := msg.(*netpack.SCChat)
	if !ok {
		return errors.New("parse chat message error")
	}

	log.Println(scChat)
	return nil
}

func (ch *ChatMessage) CreateRoom(msg proto.Message, c *Connection) error {
	scCreateChatRoom, ok := msg.(*netpack.SCCreateChatRoom)
	if !ok {
		return errors.New("parse create_chat_room message error")
	}

	if scCreateChatRoom.Error == netpack.ErrCode_SUCCESS {
		csChat := &netpack.CSChat{
			Channel: netpack.RoomChannel_CHAT_ROOM,
			Msg:     "room golang, go go go",
			RoomId:  scCreateChatRoom.RoomId,
		}

		err := c.Send(netpack.MessageID_CS_CHAT, csChat)
		if err != nil {
			return err
		}

		csJoinChatRoom := &netpack.CSJoinChatRoom{
			RoomId: scCreateChatRoom.RoomId,
		}

		err = c.Send(netpack.MessageID_CS_JOIN_CHATROOM, csJoinChatRoom)
		if err != nil {
			return err
		}
	}

	log.Println(scCreateChatRoom)
	return nil
}

func (ch *ChatMessage) JoinRoom(msg proto.Message, c *Connection) error {
	scJoinChatRoom, ok := msg.(*netpack.SCJoinChatRoom)
	if !ok {
		return errors.New("parse join_chat_room message error")
	}

	log.Println(scJoinChatRoom)
	return nil
}

type ErrorMessage struct {
	chatClient *ChatClient
}

func (e *ErrorMessage) Error(msg proto.Message, c *Connection) error {
	error_message, ok := msg.(*netpack.ErrorMessage)
	if !ok {
		return errors.New("parse error message error")
	}

	log.Println(error_message)
	return nil
}

//protoc --go_out=../im_client/ *.proto

func main() {
	log.SetFlags(log.LstdFlags | log.Lmicroseconds | log.Lshortfile)
	client := NewChatClient()
	client.Dial(1, "127.0.0.1:3399")
	client.Run()
}
